package ru.sstu.renju.core;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;

import ru.sstu.renju.config.Config;
import ru.sstu.renju.core.GameResult.Winner;

/**
 * <code>GameField</code> class represents field for Renju game.
 *
 * @author denis_murashev
 * @since Renju 1.0
 */
public class Game extends Observable implements Runnable {

	private Config config;
	private final Player[] players;
	private CellType[][] cells;
	private int moveCount;
	private GameResult result = new GameResult();
	private long delay;
	private boolean stop;
	private List<Move> history = new ArrayList<Move>();

	/**
	 * Initializes game.
	 *
	 * @param config configuration
	 */
	public Game(Config config) {
		this.config = config;
		players = new Player[] {
				config.getBlackPlayer(),
				config.getWhitePlayer(),
		};
		cells = new CellType[config.getGameSize()][config.getGameSize()];
	}

	/**
	 * @return the config
	 * @since Renju 1.1
	 */
	public Config getConfig() {
		return config;
	}

	/**
	 * @return the size of the game field
	 * @since Renju 1.1
	 */
	public int getSize() {
		return config.getGameSize();
	}

	/**
	 * @return the rowHelper
	 * @since Renju 1.1
	 */
	public RowHelper getRowHelper() {
		return new RowHelper(this);
	}

	/**
	 * Provides cell type.
	 *
	 * @param x x
	 * @param y y
	 * @return cell type
	 */
	public CellType getCell(int x, int y) {
		return cells[x][y];
	}

	/**
	 * Checks current cell.
	 *
	 * @param x x
	 * @param y y
	 * @return <code>true</code> if cell is free
	 */
	public boolean isFreeCell(int x, int y) {
		return cells[x][y] == null;
	}

	/**
	 * @return the moveCount
	 */
	public int getMoveCount() {
		return moveCount;
	}

	/**
	 * @return the result
	 */
	public GameResult getResult() {
		return result;
	}

	/**
	 * @param delay the delay to set
	 */
	public void setDelay(long delay) {
		this.delay = delay;
	}

	/**
	 * Stops the game.
	 */
	public void stop() {
		stop = true;
	}

	/**
	 * @return the history
	 */
	public List<Move> getHistory() {
		return history;
	}

	/**
	 * Provides iterator over cells in given row.
	 *
	 * @param row row
	 * @return iterator
	 */
	public Iterator<CellType> rowIterator(final Row row) {
		return new RowIterator(row);
	}

	@Override
	public void run() {
		GameStatus gameStatus = GameStatus.POSSIBLE;
		while (gameStatus == GameStatus.POSSIBLE) {
			int index = moveCount % 2;
			Player player = players[index];
			CellType type = CellType.values()[index];
			Move move = player.doMove(this, type);
			if (stop) {
				break;
			}
			if (!isFreeCell(move.getX(), move.getY())) {
				if (type == CellType.BLACK) {
					result.setWinner(Winner.WHITE);
				} else if (type == CellType.WHITE) {
					result.setWinner(Winner.BLACK);
				}
				setChanged();
				notifyObservers();
				break;
			}
			cells[move.getX()][move.getY()] = type;
			history.add(move);
			moveCount++;
			gameStatus = checkStatus();
			setChanged();
			notifyObservers();
			if (delay > 0) {
				try {
					Thread.sleep(delay);
				} catch (InterruptedException e) {
					System.err.println("Thread was interrupted");
				}
			}
		}
	}

	private GameStatus checkStatus() {
		if (moveCount == config.getGameSize() * config.getGameSize()) {
			result.setWinner(Winner.DRAW);
			return GameStatus.FINISHED;
		}
		GameStatus status = GameStatus.IMPOSSIBLE;
		for (int i = 0; i < config.getGameSize(); i++) {
			for (int j = 0; j < config.getGameSize(); j++) {
				GameStatus rowsStatus = checkRows(i, j);
				if (rowsStatus == GameStatus.POSSIBLE) {
					status = GameStatus.POSSIBLE;
				} else if (rowsStatus == GameStatus.FINISHED) {
					return GameStatus.FINISHED;
				}
			}
		}
		if (status == GameStatus.IMPOSSIBLE) {
			result.setWinner(Winner.DRAW);
			return GameStatus.FINISHED;
		}
		return GameStatus.POSSIBLE;
	}

	private GameStatus checkRows(int i, int j) {
		GameStatus status = GameStatus.IMPOSSIBLE;
		List<Row> rows = getRowHelper().shortList(i, j);
		for (Row row : rows) {
			GameStatus rowStatus = checkRow(row);
			if (rowStatus == GameStatus.FINISHED) {
				return GameStatus.FINISHED;
			}
			if (rowStatus == GameStatus.POSSIBLE) {
				status = GameStatus.POSSIBLE;
			}
		}
		return status;
	}

	private GameStatus checkRow(Row row) {
		Iterator<CellType> iterator = rowIterator(row);
		int blacks = 0;
		int whites = 0;
		while (iterator.hasNext()) {
			CellType type = iterator.next();
			if (type == CellType.BLACK) {
				blacks++;
				if (whites != 0) {
					return GameStatus.IMPOSSIBLE;
				}
			} else if (type == CellType.WHITE) {
				whites++;
				if (blacks != 0) {
					return GameStatus.IMPOSSIBLE;
				}
			}
		}
		if (blacks == config.getRowSize()) {
			result.setWinner(Winner.BLACK);
			result.setWinnigRow(row);
			return GameStatus.FINISHED;
		}
		if (whites == config.getRowSize()) {
			result.setWinner(Winner.WHITE);
			result.setWinnigRow(row);
			return GameStatus.FINISHED;
		}
		return blacks == 0 || whites == 0
				? GameStatus.POSSIBLE : GameStatus.IMPOSSIBLE;
	}

	/**
	 * <code>GameStatus</code> enumeration represents possible game and single
	 * row statuses.
	 *
	 * @author Denis_Murashev
	 * @since Renju 1.0
	 */
	private static enum GameStatus {

		/**
		 * The win is possible.
		 */
		POSSIBLE,

		/**
		 * The win is impossible.
		 */
		IMPOSSIBLE,

		/**
		 * The game is finished. No matter some player wins or it was draw.
		 */
		FINISHED;
	}

	/**
	 * <code>RowIterator</code> class represents {@link Iterator} over cells
	 * in the {@link Row}.
	 *
	 * @author Denis_Murashev
	 * @since Renju 1.0
	 */
	private final class RowIterator implements Iterator<CellType> {

		private final Row row;

		private int index;

		private RowIterator(Row row) {
			this.row = row;
		}

		@Override
		public boolean hasNext() {
			return index < config.getRowSize();
		}

		@Override
		public CellType next() {
			int x = row.getX() + row.getDirection().getDx() * index;
			int y = row.getY() + row.getDirection().getDy() * index;
			index++;
			return cells[x][y];
		}

		@Override
		public void remove() {
		}
	}
}
